home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 1 / Cream of the Crop 1.iso / PROGRAM / SYSTEM.ARJ / SYSTEMX.C
C/C++ Source or Header  |  1992-05-23  |  13KB  |  443 lines

  1. /*
  2.     NAME...
  3.         systemx - execute a program
  4.  
  5.     SYNOPSIS...
  6.         int error;
  7.         char *cmd;
  8.         error = systemx(cmd);
  9.  
  10.     NOTES...
  11.  
  12.     The command line can have redirection and/or pipes.  Temporary
  13.     files for pipes are created in the directory specified by the TEMP
  14.     environment variable, or else in the current directory.
  15.  
  16.     For internal commands the shell (found via COMSPEC) is invoked. 
  17.     (Intrinsics are assumed to be those of COMMAND.COM of MSDOS 3.3,
  18.     plus those added by MSDOS 5.0 if the current DOS version is 5.0 or
  19.     higher, or those of 4DOS if the COMSPEC includes the string
  20.     "4DOS".)
  21.  
  22.     Otherwise, the PATH will be searched for the program.  It will be
  23.     executed using the shell via the library function system() if it
  24.     turns out to be a batch file, or with spawnv() otherwise.
  25.  
  26.     Any program returning a nonzero exit status will terminate a pipe
  27.     (unless the calling program sets the external variable
  28.     execution_continues nonzero).  systemx returns the exit status of
  29.     the last program executed, or -1 if an error occurs.  In the latter
  30.     case, errno is also set as follows:
  31.  
  32.             ENOEXEC        exec format error
  33.             ENOMEM        out of memory
  34.             ENOENT        no such file or directory
  35.  
  36.     systemx is compatible with Ralf Brown's disk/XMS/EMS/ext-swapping
  37.     SPAWNO routines, which are recommended.
  38.  
  39.  
  40.     BUGS...
  41.         Could avoid big filename buffers by rearranging the user's
  42.             string (swap input and/or output file names to end and zero
  43.             terminate the substrings).
  44.         If stdin, stdout, or stderr haven't been redirected, their file
  45.             handles need not be saved.  That would leave more handles
  46.             for the child process.
  47.  
  48. */
  49.  
  50. #include <stdio.h>
  51. #include <string.h>
  52. #include <process.h>
  53. #include <errno.h>
  54. #include <io.h>
  55. #include <sys\stat.h>
  56. #include <fcntl.h>
  57. #include <stdlib.h>
  58.  
  59. #define STANDARD_SHELL    /* assume we know all the intrinsic commands */
  60.  
  61.  
  62. #ifdef DEBUG
  63. #define GRIPE(s) fprintf(stderr, s)        /* error messages */
  64. #define TR(m) printf m                    /* trace messages */
  65. #else
  66. #define GRIPE(s)
  67. #define TR(m)
  68. #endif
  69.  
  70. #define STDIN 0
  71. #define STDOUT 1
  72. #define STDERR 2
  73.  
  74. extern errno;
  75.  
  76. int execution_continues=0;    /* calling program sets this nonzero
  77.                                 if execution of programs in a pipeline
  78.                                 should continue even if a program 
  79.                                 returns a nonzero exit status */
  80.  
  81. static isfilename(int c)
  82. {    return c && (isalnum(c) || strchr("\\:\._^$~!#%&-{}()@'`", c));
  83. }
  84.  
  85. systemx(char *user_cmd)
  86. {    int val, i, err, com, bat, ext, intrinsic;
  87.     int temphandle, inhandle, outhandle;    /* file handles */
  88.     char c, *s, *t, *ptr, *tail, *cmd, *buf, *argv[3], *dp, *pp;
  89.     char oname[FILENAME_MAX];     /* name of output file */
  90.     char iname[FILENAME_MAX];     /* name of input file */
  91.     char directory[FILENAME_MAX];     /* name of one directory in the path */
  92.     char program[FILENAME_MAX];     /* name of program to execute 
  93.                                             (no extension) */
  94.     char fullpath[FILENAME_MAX];    /* full pathname of program to execute */
  95.     char nothing[]="";
  96.     struct
  97.         {char                 res[21];    /* reserved */
  98.         char                attr;        /* file attribute */
  99.         long int            datetime;    /* date & time */
  100.         long int            size;        /* file size in bytes */
  101.         char                name[13];    /* filename */
  102.         }    dta;    /* structure returned by findfirst() or findnext() */
  103.     enum {STANDARD, FILE, APPEND, PIPE} input, output;
  104.  
  105.     input = STANDARD;
  106.     
  107.     ptr = cmd = buf = strdup(user_cmd);
  108.     if(!cmd) {GRIPE("out of memory"); errno = ENOMEM; return -1;} 
  109.     while(*ptr)
  110.         {/* handle one section of the pipeline */
  111.         output = STANDARD;
  112.         while(isspace(*ptr)) ptr++;
  113.         cmd = ptr;
  114.         while(isfilename(*ptr)) ptr++;
  115.         if(ptr == cmd)
  116.             {/* no program name present */
  117.             if(input == PIPE) 
  118.                 {GRIPE("program name missing");
  119.                 errno = ENOEXEC;  /* "Exec format error" */
  120.                 return -1;
  121.                 }
  122.             break;
  123.             }
  124.  
  125.                     /* parse up to the next '|' or the end of the string */
  126.         t = tail = ptr;
  127.         while(*ptr)    
  128.             {c = *ptr++;
  129.             if(c == '"')            /* argument delimited by quotes */
  130.                 {*t++ = c;
  131.                 while(*ptr)
  132.                     {c = *t++ = *ptr++;
  133.                     if(c == '\\' && *ptr == '"')
  134.                         *t++ = *ptr++;          /* escaped quote */
  135.                     if(c == '"') break;
  136.                     }
  137.                 }
  138.             else if(c == '\\' && *ptr == '"')
  139.                 {                               /* escaped quote */
  140.                 *t++ = c; 
  141.                 *t++ = *ptr++;
  142.                 }
  143.             else if(c == '>')
  144.                 {                        /* redirected output */
  145.                 output = FILE;
  146.                 if(*ptr == '>') {ptr++; output = APPEND;}
  147.                 s = oname;
  148.                 while(isfilename(*ptr)) *s++ = *ptr++;
  149.                 *s = 0;
  150.                 }
  151.             else if(c == '<')
  152.                 {                        /* redirected input */
  153.                 if(input != STANDARD) 
  154.                     {/* standard input has already been redirected */
  155.                     GRIPE("doubly redirected input");
  156.                     errno = ENOEXEC;  /* "Exec format error" */
  157.                     return -1;
  158.                     }
  159.                 input = FILE;
  160.                 s = iname;
  161.                 while(isfilename(*ptr)) *s++ = *ptr++;
  162.                 *s = 0;
  163.                 }
  164.             else if(c == '|')
  165.                 {if(output != STANDARD) 
  166.                     {/* standard output has already been redirected */
  167.                     GRIPE("doubly redirected output");
  168.                     errno = ENOEXEC;  /* "Exec format error" */
  169.                     return -1;
  170.                     }
  171.                 output = PIPE;
  172.                 break;
  173.                 }
  174.             else *t++ = c;
  175.             }
  176.         *t = 0;
  177.  
  178.         /*
  179.             assert: 
  180.                     *cmd = first character of program name
  181.                     *tail = first character after program name
  182.                             (file redirection arguments have been removed)
  183.                     *ptr = first character after '|'
  184.                     input = STANDARD, FILE, or PIPE
  185.                     output = STANDARD, FILE, APPEND, or PIPE
  186.             If input == FILE then iname has the name of the input file.
  187.             If output==FILE or APPEND then oname has the name of the
  188.             output file.
  189.         */
  190.         strncpy(program, cmd, tail-cmd);
  191.         program[tail-cmd] = 0;
  192.  
  193.         pp = getenv("PATH");
  194.         if(!pp) pp = nothing;
  195.         dp = directory;
  196.         intrinsic = is_intrinsic(program);
  197.         TR(("%s is%s an intrinsic command\n", program, intrinsic?"":" not"));
  198.         com = ext = bat = 0;
  199.         while(!intrinsic)
  200.             {fullpath[0] = 0;
  201.             *dp = 0;
  202.             if(directory[0])
  203.                 {strcat(fullpath, directory);
  204.                 strcat(fullpath, "\\");
  205.                 }
  206.             strcat(fullpath, program);
  207.             strcat(fullpath, ".*");
  208.             TR(("looking for <%s> \n", fullpath));
  209.             err = findfirst(fullpath, &dta, 0);
  210.             while(!err)
  211.                 {
  212.                 if(strstr(dta.name, ".COM")) {com = 1; break;}
  213.                 else if(strstr(dta.name, ".EXE")) {ext = 1;}
  214.                 else if(strstr(dta.name, ".BAT")) {bat = 1;}
  215.                 err = findnext(&dta);
  216.                 }
  217.             if(com||ext||bat) break;
  218.             if( !(*pp) ) /* can't find the program in the PATH */
  219.                 {
  220. #ifdef STANDARD_SHELL
  221.                 GRIPE("program not found\n");
  222.                 errno = ENOENT;    /* "No such file or directory" */
  223.                 return -1;    /* return nonzero exit status */
  224. #else
  225.                 break;        /* try the shell (which may implement 
  226.                                 intrinsics other than those assumed here) */
  227. #endif
  228.                 }
  229.             dp = directory;
  230.             while(*pp)         /* copy next directory in path */
  231.                 {if(*pp == ';') {pp++; break;}        /* skip ';' */
  232.                 *dp++ = *pp++;
  233.                 }
  234.             }
  235.         if(s = strchr(fullpath, '*'))
  236.             {if(com) strcpy(s, "COM");
  237.             else if(ext) strcpy(s, "EXE");
  238.             else if(bat) strcpy(s, "BAT");
  239.             }
  240.  
  241.  
  242.         switch(input)
  243.             {case PIPE:
  244.             case FILE:
  245.                 temphandle = open(iname, O_RDONLY);
  246.                 if(temphandle<0) 
  247.                     {GRIPE("file not found"); 
  248.                     errno = ENOENT;    /* "No such file or directory" */
  249.                     return -1;
  250.                     }
  251.                 inhandle = dup(STDIN);        /* create duplicate handle */
  252.                 dup2(temphandle, STDIN);    /* point STDIN to input file */
  253.                 close(temphandle);            /* release extra handle */
  254.                 break;
  255.             case STANDARD:
  256.                 ;
  257.             }
  258.         flushall();
  259.         switch(output)
  260.             {case PIPE: 
  261.                 if(s = getenv("TEMP"))     /* user specified tempfile directory */
  262.                     {strncpy(oname, s, sizeof(oname) - 14);
  263.                     s = oname + strlen(oname);
  264.                     *s++ = '\\';
  265.                     }
  266.                 else s = oname;
  267.                 tmpnam(s);            /* manufacture name for pipe temporary */
  268.             case FILE:
  269.                 unlink(oname);
  270.             case APPEND:
  271.                 temphandle = open(oname, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
  272.                 if(temphandle<0) 
  273.                     {GRIPE("file creation error"); 
  274.                     errno = ENOENT;    /* "No such file or directory" */
  275.                     return -1;
  276.                     }
  277.                 outhandle = dup(STDOUT);    /* create duplicate handle */
  278.                 dup2(temphandle, STDOUT);    /* redirect stdout to the file */
  279.                 close(temphandle);            /* release extra handle */
  280.                 if(output == APPEND)
  281.                     lseek(STDOUT, 0L, 2);    /* seek to end of file */
  282.                 break;
  283.             case STANDARD:
  284.                 ;
  285.             }
  286.  
  287.         if(intrinsic || (!com && !ext))    /* including if it's a .BAT file */
  288.             {/* for an intrinsic command, we need COMMAND.COM after all */
  289.             TR(("calling system(\"%s\")\n", cmd));
  290.             val = system(cmd);
  291.             TR(("system() returns %d\n", val));
  292.             }
  293.         else
  294.             {
  295.             argv[0] = program;    /* actually, spawnv() resets this */
  296.             argv[1] = tail;
  297.             argv[2] = NULL;
  298. #ifdef DEBUG
  299.                 {printf("calling spawnv(P_WAIT, \"%s\", argv) ", fullpath);
  300.                 printf("\nwith argv = ");
  301.                 for (i = 0; argv[i]; i++)
  302.                     outstring(argv[i]);
  303.                 printf("\n");
  304.                 }
  305. #endif
  306. /*
  307.     The documentation of spawnv..() states that command arguments
  308.     should be in separate strings.  One argument of the function is an
  309.     array of pointers to those strings.  Here, I'm relying on an
  310.     undocumented feature of spawnv(): It parses the elements of argv[]
  311.     the same way command.com does.  In particular, it will subdivide
  312.     arguments containing whitespace unless they are delimited by double
  313.     quotes 
  314.                 [foo bar] -> [foo] [bar] 
  315.     it will remove the delimiting quotes
  316.                 ["foo"] -> [foo] 
  317.     and it will remove escapes from quotes
  318.                 [foo\"bar] -> [foo"bar] 
  319.     The version in Borland's library and the one by Ralf Brown act the
  320.     same.  (In fact, they handle one syntax error alike.  If given a
  321.     string with an opening quote but no closing quote, they both
  322.     terminate the argument with a carriage return).
  323.  
  324. */
  325.             val = spawnv(P_WAIT, fullpath, argv);
  326.             TR(("spawnv() returns %d\n", val));
  327.             }
  328.         flushall();
  329.         switch(input)
  330.             {case PIPE:
  331.             case FILE:
  332.                 dup2(inhandle, STDIN);    /* retore stdin to original source */
  333.                 close(inhandle);        /* release duplicate handle */
  334.                 if(input == PIPE) unlink(iname);
  335.                 break;
  336.             case STANDARD:
  337.                 ;
  338.             }
  339.         switch(output)
  340.             {case PIPE:
  341.                 strcpy(iname, oname);    /* save name of pipe temporary */
  342.                 input = PIPE;
  343.             case FILE:
  344.             case APPEND:
  345.                 dup2(outhandle, STDOUT);  /* restore stdout to original file */
  346.                 close(outhandle);          /* release the duplicate handle */
  347.                 break;
  348.             case STANDARD:
  349.                 ;
  350.             }
  351.         if(val && !execution_continues)
  352.             {if(output == PIPE) unlink(iname);
  353.             break;
  354.             }                
  355.  
  356.         }
  357.  
  358.     free(buf);
  359.     return val;
  360. }
  361.  
  362. char *(intrinsics_command[])=
  363.     {"BREAK", "CD", "CHDIR", "CLS", "COPY", "CTTY", "DATE", "DEL",
  364.     "DIR", "ECHO", "ERASE", "EXIT", "FOR", "IF", "MD", "MKDIR", "PATH",
  365.     "PAUSE", "PROMPT", "RD", "REM", "REN", "RENAME", "RMDIR", "SET",
  366.     "TIME", "TYPE", "VER", "VERIFY", "VOL", 0};
  367.  
  368. char *(intrinsics_4dos[])=
  369.     {"?", "ALIAS", "ATTRIB", "BEEP", "CALL", "CANCEL", "CDD", "CHCP",
  370.     "DESCRIBE", "DIRS", "ENDLOCAL", "ESET", "EXCEPT", "FREE", "GLOBAL",
  371.     "GOSUB", "GOTO", "HELP", "HISTORY", "INKEY", "INPUT", "KEYSTACK",
  372.     "LIST", "MEMORY", "MOVE", "POPD", "PUSHD", "QUIT", "RETURN",
  373.     "SCREEN", "SELECT", "SETDOS", "SETLOCAL", "SHIFT", "TEE", "TEXT",
  374.     "TIMER", "UNALIAS", "Y", 0};
  375.  
  376. char *(intrinsics_dos5[])=
  377.     {"CHCP", "LOADHIGH", "LH", 0};    /* these three were new with DOS 5.0 */
  378.  
  379. is_intrinsic(s) char *s;
  380. {    int i;
  381.     extern unsigned char _osmajor;
  382.     char **sv=intrinsics_command;
  383.  
  384.     for (i = 0; i < 2; i++)
  385.         {while(*sv) if(stricmp(s,*sv++)==0) return 1;
  386.         if(strstr(getenv("comspec"), "4DOS")) sv = intrinsics_4dos;
  387.         else if(_osmajor >= 5) sv = intrinsics_dos5;
  388.         else break;
  389.         }
  390.     return 0;
  391. }
  392.  
  393.  
  394. #ifdef DEBUG
  395.  
  396. /* execute systemx() from the command line */
  397.  
  398. main(int argc, char **argv)
  399. {
  400.     char buf[200];
  401.     int i, value;
  402. /*    systemx("dir|sort>sorted"); */
  403.     if(argc < 2) {fprintf(stderr, "usage: systemx  <prog>  [options]"); exit(1);}
  404.     for (i = 0; i < argc; i++)
  405.         outstring(argv[i]);
  406.     putchar('\n');
  407.     buf[0] = 0;
  408.     for (i = 1; i < argc; i++) {strcat(buf, argv[i]); strcat(buf, " ");}
  409.     fprintf(stderr, "systemx(\"%s\")\n", buf);
  410.     value = systemx(buf);
  411.     fprintf(stderr, "systemx() returns %d\n", value);
  412.     if(value == -1)
  413.         {
  414.         fprintf(stderr, "errno = %d -> ", errno);
  415. #define DISPLAY(name, val) if(errno==val) fprintf(stderr, name);    
  416.         DISPLAY("E2BIG: Arg list too long ",   20);
  417.         DISPLAY("EINVAL: Invalid argument",  19);
  418.         DISPLAY("ENOENT: No such file or directory",   2);
  419.         DISPLAY("ENOEXEC: Exec format error", 21);
  420.         DISPLAY("ENOMEM: Not enough core",   8);
  421.         }
  422. }
  423.  
  424. outstring(char *s)
  425. {    int c;
  426.     printf(" <");
  427.     while(c = *s++)
  428.         if(isprint(c)) printf("%c", c);
  429.         else printf("\\%03o", c);
  430.     printf(">");
  431. }
  432.  
  433. dump(s, n) char *s; int n;
  434. {    int col=0;
  435.     while(n--) 
  436.         {if(++col>16) {printf("\n"); col = 1;}
  437.         printf(" %02x", 0xff&*s++);
  438.         }
  439.     printf("\n");
  440. }
  441.  
  442. #endif
  443.